<template>
  <div ref="refThree" class="container"></div>
</template>

<script setup lang="ts">
import { ref, onMounted } from "vue";
import { THREE } from "@/scripts/three/toolchain"; // 引入 Three.js 库
import pureSky from "@/assets/HDRI/pureSky.jpg"; // 引入背景图像
import App from "@/scripts/App"; // 引入应用程序的主要逻辑

import { Octree } from "three/addons/math/Octree.js"; // 引入用于碰撞检测的八叉树算法
// import { OctreeHelper } from "three/addons/helpers/OctreeHelper.js"; // 引入八叉树可视化辅助工具
import { Capsule } from "three/addons/math/Capsule.js"; // 引入胶囊体（用于玩家碰撞检测）

// 变量声明部分
const refThree = ref<HTMLDivElement>(); // 用于绑定 3D 渲染容器
let app: App; // 存储应用程序实例
const modelGroup = new THREE.Group(); // 用于存储模型的 Three.js Group

// 创建八叉树实例，用于碰撞检测
const worldOctree = new Octree();

// 创建玩家的碰撞胶囊体
const playerHeight = 2; // 玩家高度，例如 2 米
const playerCollider = new Capsule(
  new THREE.Vector3(0, 0.35, 0), // 胶囊体的起始点
  new THREE.Vector3(0, playerHeight, 0), // 胶囊体的结束点
  0.5 // 胶囊体的半径
);

// 玩家速度与方向向量
const playerVelocity = new THREE.Vector3();
const playerDirection = new THREE.Vector3();

// 玩家是否在地面上的状态
let playerOnFloor = false;
let mouseTime = 0; // 用于控制鼠标的时间

// 键盘按键状态对象
const keyStates: { [key: string]: boolean } = {}; // 存储按键是否被按下

// 向量计算工具
const vector1 = new THREE.Vector3();
const vector2 = new THREE.Vector3();
const vector3 = new THREE.Vector3();

// 初始化 Three.js 场景
async function initThree() {
  if (!refThree.value) return; // 如果容器不存在，则不执行初始化
  app = new App(refThree.value); // 初始化应用实例，传入渲染容器
  app.addBackground(pureSky, true); // 添加背景图像

  // 加载模型
  const gltfModel = (await app.addModel(
    "/models/pubg_house.glb",
    modelGroup
  )) as THREE.Object3D;
  gltfModel.position.y -= 3;

  worldOctree.fromGraphNode(gltfModel);

  app.scene.add(modelGroup); // 将模型组添加到场景中

  addSceneSphere();
  addSceneLight(app.scene); // 添加场景光源

  // 鼠标按下时请求指针锁定
  refThree.value.addEventListener("mousedown", () => {
    document.body.requestPointerLock(); // 请求锁定指针
    mouseTime = performance.now(); // 记录鼠标按下的时间
  });

  app.renderer.setAnimationLoop(animate);
}

// 添加场景中的光源---------------------------------------------------------------
function addSceneLight(scene: THREE.Scene, dir: number = 1, amb: number = 0.3) {
  // 创建半球光源
  const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444);
  hemiLight.position.set(0, 300, 0);

  // 创建两个方向光源
  const dirLight1 = new THREE.DirectionalLight(0xffffff, dir);
  dirLight1.position.set(20, 500, 50);
  dirLight1.castShadow = true; // 启用阴影

  const dirLight2 = new THREE.DirectionalLight(0xffffff, dir);
  dirLight2.position.set(-20, 500, -50);
  dirLight2.castShadow = true; // 启用阴影

  // 创建环境光源
  const ambLight = new THREE.AmbientLight(0xffffff, amb);

  // 将光源添加到场景中
  scene.add(hemiLight);
  scene.add(dirLight1);
  scene.add(dirLight2);
  scene.add(ambLight);
}

const GRAVITY = 20; // 重力加速度
const NUM_SPHERES = 100; // 球体数量
const SPHERE_RADIUS = 1; // 球体半径
const STEPS_PER_FRAME = 5; // 每帧计算的子步数

// 球体的物理控制----------------------------------------------------------------
// 球的纹理
const textureLoader = new THREE.TextureLoader();
// 加载不同的贴图
const diffuseMap = textureLoader.load("/texture/Apple 2_Diffuse.jpg");
const normalMap = textureLoader.load("/texture/Apple 2_Normal.jpg");
const roughnessMap = textureLoader.load("/texture/Apple 2_Roughness.jpg");

// 设置贴图的平铺方式和偏移（调整 UV）
diffuseMap.wrapS = THREE.RepeatWrapping; // S 方向（水平）重复
diffuseMap.wrapT = THREE.RepeatWrapping; // T 方向（垂直）重复
normalMap.wrapS = THREE.RepeatWrapping;
normalMap.wrapT = THREE.RepeatWrapping;
roughnessMap.wrapS = THREE.RepeatWrapping;
roughnessMap.wrapT = THREE.RepeatWrapping;

const sphereGeometry = new THREE.IcosahedronGeometry(SPHERE_RADIUS, 5); // 球体几何体
const sphereMaterial = new THREE.MeshStandardMaterial({
  map: diffuseMap, // 漫反射贴图
  normalMap: normalMap, // 法线贴图
  roughnessMap: roughnessMap,
  roughness: 0.5,
  metalness: 0.0,
  envMapIntensity: 1.0, // 环境贴图强度（如果使用了环境贴图的话）
}); // 球体材质

type Sphere = {
  mesh: THREE.Mesh;
  collider: THREE.Sphere;
  velocity: THREE.Vector3;
};
// 球体数组
const spheres: Sphere[] = [];

let sphereIdx = 0; // 当前球体索引

function addSceneSphere() {
  // 创建球体，并添加到场景中
  for (let i = 0; i < NUM_SPHERES; i++) {
    const sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
    sphere.castShadow = true;
    sphere.receiveShadow = true;
    app.scene.add(sphere);
    spheres.push({
      mesh: sphere,
      collider: new THREE.Sphere(new THREE.Vector3(0, -100, 0), SPHERE_RADIUS), // 用球体做碰撞检测
      velocity: new THREE.Vector3(), // 球体的速度
    });
  }
}

// 投掷球体的逻辑
function throwBall() {
  const sphere = spheres[sphereIdx]; // 获取当前球体
  app.camera.getWorldDirection(playerDirection); // 获取相机的朝向
  sphere.collider.center
    .copy(playerCollider.end) // 设置球体的碰撞中心为玩家胶囊体的末端位置
    .addScaledVector(playerDirection, playerCollider.radius * 1.5); // 加上一个偏移量
  const impulse =
    15 + 30 * (1 - Math.exp((mouseTime - performance.now()) * 0.001)); // 计算投掷的冲击力
  sphere.velocity.copy(playerDirection).multiplyScalar(impulse); // 设置球体的速度
  sphere.velocity.addScaledVector(playerVelocity, 2); // 添加玩家的速度
  sphereIdx = (sphereIdx + 1) % spheres.length; // 切换到下一个球体
}

// 更新球体位置------------------------------------------------------------------
function updateSpheres(deltaTime: number) {
  spheres.forEach((sphere: Sphere) => {
    sphere.collider.center.addScaledVector(sphere.velocity, deltaTime);

    const result = worldOctree.sphereIntersect(sphere.collider);

    if (result) {
      sphere.velocity.addScaledVector(
        result.normal,
        -result.normal.dot(sphere.velocity) * 1.5
      );
      sphere.collider.center.add(result.normal.multiplyScalar(result.depth));
    } else {
      sphere.velocity.y -= GRAVITY * deltaTime;
    }

    const damping = Math.exp(-1.5 * deltaTime) - 1;
    sphere.velocity.addScaledVector(sphere.velocity, damping);

    playerSphereCollision(sphere);
  });

  spheresCollisions();

  for (const sphere of spheres) {
    sphere.mesh.position.copy(sphere.collider.center);
  }
}

// 玩家与静态世界的碰撞检测
function playerCollisions() {
  const result = worldOctree.capsuleIntersect(playerCollider); // 检查玩家胶囊体与八叉树的碰撞
  playerOnFloor = false; // 默认玩家不在地面
  if (result) {
    playerOnFloor = result.normal.y > 0; // 如果碰撞面是水平的，说明玩家在地面上
    if (!playerOnFloor) {
      playerVelocity.addScaledVector(
        result.normal,
        -result.normal.dot(playerVelocity) // 计算反弹
      );
    }
    if (result.depth >= 1e-10) {
      playerCollider.translate(result.normal.multiplyScalar(result.depth)); // 将玩家胶囊体移到碰撞位置
    }
  }
}

// 玩家球体碰撞
function playerSphereCollision(sphere: Sphere) {
  const center = vector1
    .addVectors(playerCollider.start, playerCollider.end)
    .multiplyScalar(0.5);

  const sphere_center = sphere.collider.center;

  const r = playerCollider.radius + sphere.collider.radius;
  const r2 = r * r;

  // approximation: player = 3 spheres

  for (const point of [playerCollider.start, playerCollider.end, center]) {
    const d2 = point.distanceToSquared(sphere_center);

    if (d2 < r2) {
      const normal = vector1.subVectors(point, sphere_center).normalize();
      const v1 = vector2
        .copy(normal)
        .multiplyScalar(normal.dot(playerVelocity));
      const v2 = vector3
        .copy(normal)
        .multiplyScalar(normal.dot(sphere.velocity));

      playerVelocity.add(v2).sub(v1);
      sphere.velocity.add(v1).sub(v2);

      const d = (r - Math.sqrt(d2)) / 2;
      sphere_center.addScaledVector(normal, -d);
    }
  }
}

// 球体间碰撞效果
function spheresCollisions() {
  for (let i = 0, length = spheres.length; i < length; i++) {
    const s1 = spheres[i];

    for (let j = i + 1; j < length; j++) {
      const s2 = spheres[j];

      const d2 = s1.collider.center.distanceToSquared(s2.collider.center);
      const r = s1.collider.radius + s2.collider.radius;
      const r2 = r * r;

      if (d2 < r2) {
        const normal = vector1
          .subVectors(s1.collider.center, s2.collider.center)
          .normalize();
        const v1 = vector2.copy(normal).multiplyScalar(normal.dot(s1.velocity));
        const v2 = vector3.copy(normal).multiplyScalar(normal.dot(s2.velocity));

        s1.velocity.add(v2).sub(v1);
        s2.velocity.add(v1).sub(v2);

        const d = (r - Math.sqrt(d2)) / 2;

        s1.collider.center.addScaledVector(normal, d);
        s2.collider.center.addScaledVector(normal, -d);
      }
    }
  }
}

// 更新玩家的位置与速度
function updatePlayer(deltaTime: any) {
  let damping = Math.exp(-4 * deltaTime) - 1; // 阻尼系数（影响速度衰减）
  if (!playerOnFloor) {
    playerVelocity.y -= GRAVITY * deltaTime; // 重力影响
    damping *= 0.1; // 空气阻力
  }
  playerVelocity.addScaledVector(playerVelocity, damping); // 更新玩家速度
  const deltaPosition = playerVelocity.clone().multiplyScalar(deltaTime); // 计算位移
  playerCollider.translate(deltaPosition); // 更新玩家胶囊体的位置
  playerCollisions(); // 处理玩家与世界的碰撞
  app.camera.position.copy(playerCollider.end); // 更新相机位置
}

// MARK: 胶囊体传送 -------------------------------------------------------------
function teleportPlayerIfOob() {
  if (app.camera.position.y <= -25) {
    playerCollider.start.set(0, 0.35, 0);
    playerCollider.end.set(0, 1, 0);
    playerCollider.radius = 0.35;
    app.camera.position.copy(playerCollider.end);
    app.camera.rotation.set(0, 0, 0);
  }
}

// 事件监听部分------------------------------------------------------------------

// 控制玩家移动
function controls(deltaTime: any) {
  const speedDelta = deltaTime * (playerOnFloor ? 25 : 8); // 根据是否在地面上调整移动速度
  if (keyStates["KeyW"]) {
    playerVelocity.add(getForwardVector().multiplyScalar(speedDelta)); // 向前移动
  }
  if (keyStates["KeyS"]) {
    playerVelocity.add(getForwardVector().multiplyScalar(-speedDelta)); // 向后移动
  }
  if (keyStates["KeyA"]) {
    playerVelocity.add(getSideVector().multiplyScalar(-speedDelta)); // 向左移动
  }
  if (keyStates["KeyD"]) {
    playerVelocity.add(getSideVector().multiplyScalar(speedDelta)); // 向右移动
  }
  if (playerOnFloor && keyStates["Space"]) {
    playerVelocity.y = 15; // 按下空格键时，跳跃
  }
}

// 获取玩家前进方向的向量
function getForwardVector() {
  app.camera.getWorldDirection(playerDirection); // 获取相机的朝向
  playerDirection.y = 0; // 忽略垂直方向
  playerDirection.normalize(); // 标准化方向向量
  return playerDirection;
}

// 获取玩家侧向移动的向量
function getSideVector() {
  app.camera.getWorldDirection(playerDirection); // 获取相机的朝向
  playerDirection.y = 0; // 忽略垂直方向
  playerDirection.normalize(); // 标准化方向向量
  playerDirection.cross(app.camera.up); // 计算侧向向量
  return playerDirection;
}

// 监听键盘按键事件
document.addEventListener("keydown", (event) => {
  keyStates[event.code] = true; // 按键按下时标记按键状态为 true
});

document.addEventListener("keyup", (event) => {
  keyStates[event.code] = false; // 按键松开时标记按键状态为 false
});

// 鼠标释放时投掷球体
document.addEventListener("mouseup", () => {
  if (document.pointerLockElement !== null) throwBall(); // 如果指针锁定，投掷球体
});

// 控制镜头旋转逻辑--------------------------------------------------------------

const mouseSensitivity = 0.002; // 鼠标灵敏度
let yaw = 0; // 水平旋转
let pitch = 0; // 垂直旋转
const maxPitch = Math.PI / 2 - 0.1; // 最大俯仰角，防止反向旋转
// 鼠标移动时更新相机视角
let isMoving = false;
let lastTime = 0;
const moveThrottle = 1000 / 60; // 限制在60fps

// 存储鼠标移动的累积值
let accumulatedX = 0;
let accumulatedY = 0;

document.body.addEventListener("mousemove", (event) => {
  if (document.pointerLockElement === document.body) {
    // 累积鼠标移动值
    accumulatedX += event.movementX * mouseSensitivity;
    accumulatedY += event.movementY * mouseSensitivity;

    if (!isMoving) {
      isMoving = true;
      requestAnimationFrame(updateCamera);
    }
  }
});

function updateCamera(currentTime: number) {
  if (!isMoving) return;

  // 检查是否需要更新
  if (currentTime - lastTime < moveThrottle) {
    requestAnimationFrame(updateCamera);
    return;
  }

  // 更新旋转角度
  if (Math.abs(accumulatedX) > 0.01 || Math.abs(accumulatedY) > 0.01) {
    yaw -= accumulatedX;
    pitch -= accumulatedY;

    // 限制俯仰角度
    pitch = Math.max(-maxPitch, Math.min(maxPitch, pitch));

    // 使用缓存的四元数对象
    updateCameraRotation();

    // 重置累积值
    accumulatedX *= 0.5; // 添加一些缓动效果
    accumulatedY *= 0.5;

    lastTime = currentTime;
    requestAnimationFrame(updateCamera);
  } else {
    isMoving = false;
    accumulatedX = 0;
    accumulatedY = 0;
  }
}

// 缓存四元数对象以避免重复创建
const _rotation = new THREE.Quaternion();
const _yawQuat = new THREE.Quaternion();
const _pitchQuat = new THREE.Quaternion();
const _euler = new THREE.Euler();

function updateCameraRotation() {
  // 使用缓存的对象更新旋转
  _euler.set(0, yaw, 0, "YXZ");
  _yawQuat.setFromEuler(_euler);

  _euler.set(pitch, 0, 0, "YXZ");
  _pitchQuat.setFromEuler(_euler);

  _rotation.multiplyQuaternions(_yawQuat, _pitchQuat);

  // 平滑插值过渡
  app.camera.quaternion.slerp(_rotation, 0.1);
}
// 动画循环----------------------------------------------------------------------

// 创建一个时钟对象，来处理时间差
const clock = new THREE.Clock();

const animate = () => {
  const deltaTime = Math.min(0.05, clock.getDelta()) / STEPS_PER_FRAME;

  // 为了避免高速物体穿过障碍物，进行多步碰撞检测
  for (let i = 0; i < STEPS_PER_FRAME; i++) {
    // 控制玩家移动
    controls(deltaTime);
    // 更新玩家的位置与速度
    updatePlayer(deltaTime);

    updateSpheres(deltaTime);
    teleportPlayerIfOob();
  }
  app?.render(); // 渲染场景
};

// 组件挂载时初始化 Three.js 场景
onMounted(() => {
  initThree(); // 调用初始化函数
});
</script>

<style scoped>
.container {
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
  margin: 0;
  padding: 0;
  overflow: hidden;
}
</style>
